package com.ysy.serviceImpl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ysy.config.ThreadPoolConfig;
import com.ysy.constant.RedisConstant;
import com.ysy.constant.RocketMqConstant;
import com.ysy.context.SecurityContextHolder;
import com.ysy.entity.NoteCollect;
import com.ysy.entity.dto.NoteCollectDTO;
import com.ysy.entity.vo.NoteCollectReqVO;
import com.ysy.enums.NoteCollectEnum;
import com.ysy.enums.ResponseCodeEnum;
import com.ysy.enums.YesOrNoEnum;
import com.ysy.mapper.NoteCollectMapper;
import com.ysy.persimmons.common.exception.ServiceException;
import com.ysy.persimmons.common.response.Response;
import com.ysy.persimmons.common.util.AssertUtil;
import com.ysy.persimmons.common.util.DateUtils;
import com.ysy.service.NoteCollectService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ysy.service.NoteService;
import jakarta.annotation.Resource;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 笔记收藏表 服务实现类
 * </p>
 *
 * @author yangSy
 * @since 2025-05-01
 */
@Service
public class NoteCollectServiceImpl extends ServiceImpl<NoteCollectMapper, NoteCollect> implements NoteCollectService {

    private static final Logger log = LoggerFactory.getLogger(NoteCollectServiceImpl.class);


    @Resource
    private RedisTemplate<String,String> redisTemplate;
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private NoteService noteService;
    @Resource
    private NoteCollectMapper noteCollectMapper;

    @Autowired
    @Qualifier(ThreadPoolConfig.NOTE_EXECUTOR)
    private ThreadPoolTaskExecutor noteExecutor;

    @Override
    public Response<?> noteCollectAdd(NoteCollectReqVO reqVO) {
        Long noteId = reqVO.getNoteId();
        Long userId = SecurityContextHolder.getUserId();
        // 收藏的笔记是否存在
        Long creatorId = noteService.checkNoteExistAndGetCreatorId(noteId);
        // 不能收藏自己的笔记
        AssertUtil.isFalse(Objects.equals(userId, creatorId), ResponseCodeEnum.COLLECT_IS_ME_NOTE);
        // 是否已经收藏过了该笔记

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/collect/roaring_bitmap_note_collect_check_add.lua")));
        script.setResultType(Long.class);
        // 用户的收藏redis存储
        String userCollectRoaringBitmapKey = RedisConstant.buildRoaringBitmapNoteCollectKey(userId);
        LocalDateTime now = LocalDateTime.now();
        long expires = DateUtils.localDateTimeTimestamp(now);
        long collectResult = redisTemplate.execute(script, Collections.singletonList(userCollectRoaringBitmapKey), noteId,expires);
        NoteCollectEnum noteCollectEnum = NoteCollectEnum.get(collectResult);
        AssertUtil.isFalse(Objects.isNull(noteCollectEnum),ResponseCodeEnum.SYSTEM_ERROR);
        switch (noteCollectEnum) {
            case NOTE_ALREADY_COLLECT -> throw new ServiceException(ResponseCodeEnum.NOTE_ALREADY_COLLECT);
            case NO_EXISTS -> {
                // 查询数据库
                Long count = noteCollectMapper.selectCount(new QueryWrapper<NoteCollect>().eq("user_id", userId).eq("note_id", noteId));
                AssertUtil.isFalse(Objects.isNull(count),ResponseCodeEnum.SYSTEM_ERROR);
                // 保底1天+随机秒数
                long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
                if ( count > 0){
                    // 同步跟新位图缓存
                    // 进行位图缓存的更新，正常一个人不会频繁去点多个取消
                    noteExecutor.execute(() -> {
                        batchAddNoteCollectBitmapAndExpire(userId,userCollectRoaringBitmapKey, expireSeconds);
                    });
                    throw new ServiceException(ResponseCodeEnum.NOTE_ALREADY_COLLECT);
                }
                // 这里是数据库也没有收藏，进行收藏，并且把当前这个收藏加入
                batchAddNoteCollectBitmapAndExpire(userId,userCollectRoaringBitmapKey, expireSeconds);
                // 再执行一次
                redisTemplate.execute(script, Collections.singletonList(userCollectRoaringBitmapKey), noteId,expires);
            }
            // 操作收藏成功和未收藏
        }
        // 更新redis中的数据
        DefaultRedisScript<Long> script2 = new DefaultRedisScript<>();
        script2.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/collect/z_set_note_collect_check_add.lua")));
        script2.setResultType(Long.class);
        // 用户的收藏redis存储
        String userCollectRedisKey = RedisConstant.buildUserNoteCollectKey(userId);
        long zSetResult = redisTemplate.execute(script2, Collections.singletonList(userCollectRedisKey), noteId,expires);
        NoteCollectEnum noteZSetCollectEnum = NoteCollectEnum.get(zSetResult);
        AssertUtil.isFalse(Objects.isNull(noteZSetCollectEnum),ResponseCodeEnum.SYSTEM_ERROR);
        if (noteZSetCollectEnum.equals(NoteCollectEnum.NO_EXISTS)) {
            // 查询数据库判断
            // 查询当前用户最新收藏的 100 篇笔记
            List<NoteCollect> noteCollectionList = noteCollectMapper.selectCollectedByUserIdAndLimit(userId, 100);

            if (CollUtil.isNotEmpty(noteCollectionList)) {
                // 保底1天+随机秒数
                long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
                // 构建 Lua 参数
                Object[] luaArgs = buildNoteCollectZSetLuaArgs(noteCollectionList, expireSeconds);

                DefaultRedisScript<Long> script3 = new DefaultRedisScript<>();
                // Lua 脚本路径
                script3.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/collect/z_set_batch_add_note_collect_and_expire.lua")));
                // 返回值类型
                script3.setResultType(Long.class);

                redisTemplate.execute(script3, Collections.singletonList(userCollectRedisKey), luaArgs);

                // 再次调用lua 脚本，将当前收藏的笔记添加到 zSet 中
                redisTemplate.execute(script2, Collections.singletonList(userCollectRedisKey), noteId, DateUtils.localDateTimeTimestamp(now));
            }
            // 更新
        }
        // 执行mq落库
        NoteCollectDTO build = NoteCollectDTO.builder()
                .noteId(noteId)
                .userId(userId)
                .noteCreatorId(creatorId)
                .type(YesOrNoEnum.YES.getCode())
                .createTime(now)
                .build();

        MessageBuilder<String> stringMessageBuilder = MessageBuilder.withPayload(JSON.toJSONString(build));
        rocketMQTemplate.asyncSendOrderly(RocketMqConstant.NOTE_COLLECT_TOPIC, stringMessageBuilder, String.valueOf(userId), new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {

            }
            @Override
            public void onException(Throwable throwable) {
            }
        });

        return Response.success();
    }

    /**
     * 构建zset数据
     * @param noteCollectionList
     * @param expireSeconds
     * @return
     */
    private Object[] buildNoteCollectZSetLuaArgs(List<NoteCollect> noteCollectionList, long expireSeconds) {
        int size = noteCollectionList.size();
        Object[] luaArgs = new Object[size];
        int i = 0;
        for (NoteCollect noteCollect : noteCollectionList) {
            luaArgs[i]=DateUtils.localDateTimeTimestamp(noteCollect.getCreateTime());
            luaArgs[i+1]=noteCollect.getNoteId();
            i+=2;
        }
        luaArgs[size-1]=expireSeconds;
        return luaArgs;
    }


    @Override
    public Response<?> noteCollectCancel(NoteCollectReqVO reqVO) {
        Long noteId = reqVO.getNoteId();
        Long userId = SecurityContextHolder.getUserId();
        // 收藏的笔记是否存在
        Long creatorId = noteService.checkNoteExistAndGetCreatorId(noteId);
        // 是否已经收藏过了该笔记
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/collect/roaring_bitmap_note_collect_check_cancel.lua")));
        script.setResultType(Long.class);
        // 用户的收藏redis存储
        String userCollectRoaringBitmapKey = RedisConstant.buildRoaringBitmapNoteCollectKey(userId);
        LocalDateTime now = LocalDateTime.now();
        long expires = DateUtils.localDateTimeTimestamp(now);
        long collectResult = redisTemplate.execute(script, Collections.singletonList(userCollectRoaringBitmapKey), noteId,expires);
        NoteCollectEnum noteCollectEnum = NoteCollectEnum.get(collectResult);
        AssertUtil.isFalse(Objects.isNull(noteCollectEnum),ResponseCodeEnum.SYSTEM_ERROR);
        switch (noteCollectEnum) {
            case NOTE_UN_COLLECT -> throw new ServiceException(ResponseCodeEnum.NOTE_NO_COLLECT);
            case NO_EXISTS -> {
                // 查询数据库
                Long count = noteCollectMapper.selectCount(new QueryWrapper<NoteCollect>().eq("user_id", userId).eq("note_id", noteId));
                AssertUtil.isFalse(Objects.isNull(count),ResponseCodeEnum.SYSTEM_ERROR);
                // 保底1天+随机秒数
                long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
                if ( count < 1){
                    // 同步跟新位图缓存
                    // 进行位图缓存的更新，正常一个人不会频繁去点多个取消
                    noteExecutor.execute(() -> {
                        batchAddNoteCollectBitmapAndExpire(userId,userCollectRoaringBitmapKey, expireSeconds);
                    });
                    throw new ServiceException(ResponseCodeEnum.NOTE_NO_COLLECT);
                }
                // 这里是数据库也有收藏，进行收藏，并且把当前这个收藏加入
                batchAddNoteCollectBitmapAndExpire(userId,userCollectRoaringBitmapKey, expireSeconds);
                // 再执行一次，,取消收藏
                redisTemplate.execute(script, Collections.singletonList(userCollectRoaringBitmapKey), noteId,expires);
            }

        }
        // 更新redis中的数据
        String collectRedisZSetKey = RedisConstant.buildUserNoteCollectKey(userId);
        redisTemplate.opsForZSet().remove(collectRedisZSetKey, noteId);

        // 执行mq落库
        NoteCollectDTO build = NoteCollectDTO.builder()
                .noteId(noteId)
                .userId(userId)
                .noteCreatorId(creatorId)
                .type(YesOrNoEnum.NO.getCode())
                .createTime(now)
                .build();

        MessageBuilder<String> stringMessageBuilder = MessageBuilder.withPayload(JSON.toJSONString(build));
        rocketMQTemplate.asyncSendOrderly(RocketMqConstant.NOTE_COLLECT_TOPIC, stringMessageBuilder, String.valueOf(userId), new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {

            }
            @Override
            public void onException(Throwable throwable) {
            }
        });

        return Response.success();
    }


    /**
     * 批量更新collect的位图信息
     * @param userId
     * @param userCollectRedisKey
     * @param expireSeconds
     */
    private void batchAddNoteCollectBitmapAndExpire(Long userId, String userCollectRedisKey, long expireSeconds) {
        List<NoteCollect> noteCollectList  = noteCollectMapper.selectList(new QueryWrapper<NoteCollect>().eq("user_id", userId));
        if (io.jsonwebtoken.lang.Collections.isEmpty(noteCollectList)) {
            return;
        }
        List<Object> luaArgs = Lists.newArrayList();
        noteCollectList.forEach(noteCollect -> {
            luaArgs.add(noteCollect.getNoteId());
        });
        luaArgs.add(expireSeconds);
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/collect/roaring_bitmap_batch_add_note_collect.lua")));
        script.setResultType(Long.class);
        redisTemplate.execute(script,Collections.singletonList(userCollectRedisKey),luaArgs.toArray());
    }
}
